using Tyng.Data.Utilities;
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Serialization;

namespace Tyng.Data.Utilities
{
    public sealed class ScriptList
    {
        List<Script> _scripts = new List<Script>();
        List<string> _circulars = new List<string>();
        List<string> _files = new List<string>();

        DatabaseMap _dbs;
        bool _circularCheck = false;
        string _ignore;

        internal ScriptList(DatabaseMap dbs, bool circularCheck, string ignore)
        {
#if OUTPUT
			Console.WriteLine("\n\nGenerating script list:");
#endif

            _dbs = dbs;
            _circularCheck = circularCheck;
            _ignore = ignore;

            foreach (Database db in dbs)
            {
                string firstFile = Path.Combine(db.Path, Constants.FirstFile);
                string lastFile = Path.Combine(db.Path, Constants.LastFile);

                if (File.Exists(firstFile))
                    AddFile(db, null, firstFile);

                AddFiles(db, db.Path);

                if (File.Exists(lastFile))
                    AddFile(db, null, lastFile);
            }

#if OUTPUT
			Console.WriteLine("Finished generating script list.\n\n");
#endif
        }

        #region Static Methods
        public static string[] GetCircularDependencies(DatabaseMap dbs)
        {
            ScriptList sl = new ScriptList(dbs, true, null);

            return sl._circulars.ToArray();
        }

        public static Script[] GetScriptsInOrder(DatabaseMap dbs)
        {
            return GetScriptsInOrder(dbs, null);
        }

        public static Script[] GetScriptsInOrder(DatabaseMap dbs, string ignoreDirectories)
        {
            ScriptList sl = new ScriptList(dbs, false, ignoreDirectories);

            return sl._scripts.ToArray();
        }

        #endregion

        internal DatabaseMap Databases { get { return _dbs; } }
        internal List<Script> Scripts { get { return _scripts; } }

        internal void AddFile(Database db, Stack<string> stack, string file)
        {
            try
            {
                if (stack == null)
                    stack = new Stack<string>();
#if OUTPUT
			Console.Write("{0}Adding file {1} from database {2}", new string('\t', stack.Count), file, db.Name);

			int lineCount = 0;
			int blockCount = 0;
#endif

                if (stack.Contains(file))
                {
#if OUTPUT
				Console.WriteLine("...circular detected");
#endif

                    if (!_circularCheck)
                        throw new Exception(string.Format("Circular	reference for file {0}", file));
                    else
                    {
                        List<string> circular = new List<string>(stack.ToArray());

                        circular.Sort();

                        string temp = string.Join("; ", circular.ToArray());

                        if (!_circulars.Contains(temp))
                            _circulars.Add(temp);

                        return;
                    }
                }

                if (!_circularCheck && _files.Contains(file.ToUpper()))
                {
#if OUTPUT
				Console.WriteLine("...already added");
#endif
                    return;
                }
#if OUTPUT
			else
				Console.WriteLine();
#endif

                string ext = Path.GetExtension(file).ToUpper();

                if (Array.IndexOf(Constants.DatabaseObjectTypeExtensions, ext) >= 0)
                {

                    stack.Push(file);


                    if (ext.Equals(Constants.SeedDataExtension, StringComparison.OrdinalIgnoreCase))
                    {
                        ParseSeedDataScript(db, file, stack);
                    }
                    else
                    {
                        ParseNormalScript(db, file, stack);
                    }

                    _files.Add(file.ToUpper());

                    stack.Pop();
                }
            }
            catch (Exception exc)
            {
                throw new Exception(string.Format("Error adding file {0} to script list: {1}", file, exc.Message), exc);
            }
        }

        private void ParseSeedDataScript(Database db, string file, Stack<string> stack)
        {
            string configFile = file + Constants.ConfigFileExtension;

            using (TextReader config = File.OpenText(configFile))
            {
                using (TextReader data = File.OpenText(file))
                {
                    ScriptList.ParseSeedDataScript(this, this.Scripts, db, file, config, data, stack);
                }
            }
        }

        public static string[] ParseSeedDataScript(TextReader configData, TextReader seedData)
        {
            List<Script> scripts = new List<Script>();
            ParseSeedDataScript(null, scripts, null, null, configData, seedData, null);

            string[] returnData = new string[scripts.Count];

            for (int i = 0; i < scripts.Count; i++)
            {
                returnData[i] = scripts[i].Text;
            }

            return returnData;
        }

        private static void ParseSeedDataScript(ScriptList sl, List<Script> scripts, Database db, string fileName, TextReader configData, TextReader seedData, Stack<string> stack)
        {
            //determine version..
            string ns = string.Empty;

            string config = configData.ReadToEnd();

            configData = new StringReader(config);

            //check for config
            using (XmlReader xml = XmlReader.Create(configData))
            {
                while (xml.NodeType != XmlNodeType.Element)
                    xml.Read();

                ns = xml.NamespaceURI;
            }

            configData = new StringReader(config);
            
            ISeedDataParser parser = null;

            //TODO: move this to config file or something maybe?
            switch (ns)
            {
                case "http://domin-8.com/xmlns/data/utilities/SeedData":
                    parser = new SeedDataParser();
                    break;
                default:
                    throw new InvalidOperationException("Invalid config file format, unsupported / unset namespace");
            }

            parser.Parse(sl, scripts, db, fileName, configData, seedData, stack);
        }

        void ParseNormalScript(Database db, string file, Stack<string> stack)
        {
            using (StreamReader sr = File.OpenText(file))
            {
                string line = sr.ReadLine();
                StringBuilder block = new StringBuilder();

                while (line != null)
                {
#if OUTPUT
					lineCount++;
#endif

                    if (line.StartsWith(Constants.DependencyPrefix))
                    {
                        //End current block
                        if (block.ToString().Trim('\n', '\r', '\t', ' ').Length > 0)
                        {
                            _scripts.Add(new Script(db, file, File.GetLastWriteTime(file), block.ToString()));
#if OUTPUT
								blockCount++;
#endif
                        }

                        block = new StringBuilder();

                        //Begin dependency parsing...
                        string depend = line.Substring(Constants.DependencyPrefix.Length);
                        string[] parts = depend.Split(':');

                        string dependDbName = parts[0];
                        string dependFileName = parts[1];

                        Database dependDatabase = _dbs[dependDbName];

                        string fullFile = Path.Combine(dependDatabase.Path, dependFileName);

                        if (!File.Exists(fullFile))
                            throw new Exception(string.Format("Dependency {0} for file {1} does not exist", dependFileName, Path.GetFileName(file)));

                        AddFile(dependDatabase, stack, fullFile);
                    }
                    else if (!_circularCheck)
                    {
                        if (line.Trim().ToUpper() == "GO")
                        {
                            //End current block on GO
                            if (block.ToString().Trim('\n', '\r', '\t', ' ').Length > 0)
                            {
                                _scripts.Add(new Script(db, file, File.GetLastWriteTime(file), block.ToString()));
#if OUTPUT
								blockCount++;
#endif
                            }

                            block = new StringBuilder();
                        }
                        else
                            block.Append("\n" + line); //append line to current block
                    }
                    line = sr.ReadLine(); //read next line
                }

                //append remaining block if anything left over in StringBuilder
                if (!_circularCheck && block.ToString().Trim('\n', '\r', '\t', ' ').Length > 0)
                {
                    _scripts.Add(new Script(db, file, File.GetLastWriteTime(file), block.ToString()));
#if OUTPUT
					blockCount++;
#endif
                }
            }
        }

        void AddFiles(Database db, string dir)
        {
            string ignoreDirectoriesList = _ignore;

            if (ignoreDirectoriesList == null)
                ignoreDirectoriesList = HybridSettings.GetValue("IgnoreDirectories");

            string[] ignoreDirectories = new string[] { };

            if (ignoreDirectoriesList != null && ignoreDirectoriesList.Length > 0)
                ignoreDirectories = ignoreDirectoriesList.Split(';');

            for (int i = 0; i < ignoreDirectories.Length; i++)
                ignoreDirectories[i] = Path.Combine(db.Path, ignoreDirectories[i]).ToUpper();

            string[] files = Directory.GetFiles(dir);

            foreach (string file in files)
            {
                string ext = Path.GetExtension(file).ToUpper();

                if (Path.GetFileName(file).ToUpper() != Constants.FirstFile && Path.GetFileName(file).ToUpper() != Constants.LastFile && Array.IndexOf(Constants.DatabaseObjectTypeExtensions, ext) >= 0)
                    AddFile(db, null, file);
            }

            string[] dirs = Directory.GetDirectories(dir);

            foreach (string subDir in dirs)
            {
                string upper = subDir.ToUpper();
                if (Array.IndexOf(ignoreDirectories, upper) < 0)
                    AddFiles(db, subDir);
            }
        }
    }
}
